<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>d3 demo</title>
<style>
body{
margin:0;
padding:0;
overflow:hidden;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var nodes = [
{id:1,r:30,pic:"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2690587153,2643787055&fm=26&gp=0.jpg"},
{id:2,r:30,pic:"https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2860640912,1833965317&fm=26&gp=0.jpg"},
{id:3,r:30,pic:"https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=297690209,1035022668&fm=26&gp=0.jpg"}
];
var links = [{source:1,target:2},{source:1,target:3},];

var simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collide", d3.forceCollide(100).strength(0.2).iterations(5))
.alphaDecay(0.0228)
.on("tick", () => ticked());

var svg = d3.select("body")
	.append("svg")
	.attr("width",width)
	.attr("height",height);
	
var defs = svg.append("defs");

var patterns = defs
	.selectAll("pattern.patternclass")
	.data(nodes)
	.enter()
	.append("pattern")
	.attr("class","patternclass")
	.attr("id",d=>"ava"+d.id)
	.attr('patternUnits', 'objectBoundingBox')
	.attr("x", "0")
	.attr("y", "0")
	.attr("width", "1")
	.attr("height", "1");

var marker = defs
	.append("marker")
	.attr("id","marker")
	.attr("markerWidth",10)
	.attr("markerHeight",10)
	.attr("refX",36)
	.attr("refY",4)
	.attr("orient","auto")
	.attr("markerUnits","userSpaceOnUse")
	.append("path")
	.attr("d","M 0 0 8 4 0 8Z")
	.attr("fill", "steelblue");

patterns.append("image")
	.attr("xlink:href",d=>d.pic)
	.attr("src",d=>d.pic)
	.attr("width",d=>d.r*2)
	.attr("height",d=>d.r*2)
	.attr("preserveAspectRatio", "xMidYMin slice");

//总容器
var holders = svg.append("g")
	.attr("class","holders")
	.attr("width",width)
	.attr("height",height);
	
var edges = holders.selectAll("g.edge")
			.data(links)
			.enter()
			.append("g")
			.attr("class","edge")
			.attr("fill","#bad4ed");
			
var links = edges.append("path")
			.attr("class","links")
			.style("marker-end", "url(#marker)")
			.attr("stroke","#bad4ed");

var nodes = holders.selectAll("circle.circleclass")
			.data(nodes)
			.enter()
			.append("circle")
			.attr("class","circleclass")
			.attr("fill",d=>("url(#ava"+d.id+")"))
			.attr("r",d=>d.r)
			.attr("stroke","#ccc")
			.attr("stroke-width",2)
			.call(
				d3.drag()
				.on("start",dragStart)
				.on("end",dragEnd)
				.on("drag",draged)
			);
			
function ticked(){
	nodes.attr("cx",d=>d.x)
	.attr("cy",d=>d.y);
	
	edges.attr("transform",d=>{
		return getTransform(d.source, d.target, getDis(d.source, d.target));
	});
	
	 links.attr("d", d => {
            return "M3,0 L"+getDis(d.source, d.target)+",0";
     });
}

// 求元素移动到目标位置所需要的 transform 属性值
function getTransform(source, target, _dis) {
    var r;
    if (target.x > source.x) {
        if (target.y > source.y) {
            r = Math.asin((target.y - source.y) / _dis)
        } else {
            r = Math.asin((source.y - target.y) / _dis);
            r = -r;
        }
    } else {
        if (target.y > source.y) {
            r = Math.asin((target.y - source.y) / _dis);
            r = Math.PI - r;
        } else {
            r = Math.asin((source.y - target.y) / _dis);
            r -= Math.PI;
        }
    }
    r = r * (180 / Math.PI);
    return "translate(" + source.x + "," + source.y + ")rotate(" + r + ")";
}

// 求两点间的距离
function getDis(s, t) {
    return Math.sqrt((s.x - t.x) * (s.x - t.x) + (s.y - t.y) * (s.y - t.y));
}
		
function dragStart(d){
	d3.event.sourceEvent.stopPropagation();
	if (!d3.event.active) simulation.alphaTarget(0.3).restart();
	d.fx = d.x;
    d.fy = d.y;
}
function dragEnd(d){
	if (!d3.event.active) simulation.alphaTarget(0);
	d.fx = null;
    d.fy = null;
}
function draged(d){
	d.fx = d3.event.x;
    d.fy = d3.event.y;
}	
</script>
</body>
</html>